package com.jinhua.config;

import com.jinhua.lombok.extern.slf4j.Slf4j;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.mvel2.MVEL;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.LocalVariableTableParameterNameDiscoverer;
import org.springframework.core.annotation.AnnotationUtils;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * 组装实体类的拦截器
 */
@Configuration
@Aspect
@Slf4j
public class ConsumeEntityInterceptor {


	private final Map<String, Object> cacheMap = new ConcurrentHashMap<>();
	private final Map<String, Map<String, Boolean>> fieldsCache = new ConcurrentHashMap<>();
//	private Log log = Log.getInstance(getClass());

	/**
	 * 拦截条件 第一个是方法 第二个是类拦截
	 */
	@Pointcut("(execution(@com.microservice.commons.annotation.http.Consume * *(..))) || (within(@com.microservice.commons.annotation.http.Consume *))")
	public void consumeEntity() {

	}

	/**
	 * 微服务具体调用拦截
	 *
	 * @param joinPoint
	 * @return
	 */
	@Around("consumeEntity()")
	public Object parseEntity(ProceedingJoinPoint joinPoint) throws Throwable {
		Class<?> newInstanceClass = getConsume(joinPoint);


		if (newInstanceClass == null) {
//			log.warn("class has no @Consume annotation");
			return null;
		}

		String execClassName = joinPoint.getSignature().getDeclaringTypeName();
		String execMethodName = joinPoint.getSignature().getName();
		String key = execClassName + execMethodName;


		Object[] args = joinPoint.getArgs();

		String expression;
		Map<String, Object> valExec;
		Object execVal = cacheMap.get(key);
		if (execVal != null) {
			expression = (String) execVal;
			Map<String, Object> tmpMap = (Map<String, Object>) cacheMap.get(key + "_map");
			valExec = new HashMap<>(tmpMap.size() << 1);

			valExec.put("__obj", newInstanceClass.newInstance());
			int i = 0;
			for (Map.Entry<String, Object> entry : tmpMap.entrySet()) {

				valExec.put(entry.getKey(), args[i++]);
			}


		} else {
			String methodName = execMethodName;


			Class<?> reflectClass = Class.forName(execClassName);
			LocalVariableTableParameterNameDiscoverer parameterNameDiscoverer =
				new LocalVariableTableParameterNameDiscoverer();
			Map<String, Boolean> allFields = getAllFields(newInstanceClass);

			Method[] methods = reflectClass.getDeclaredMethods();
			Method paramMethod = null;
			for (Method method : methods) {

				if (method.getName().equals(methodName)) {
					paramMethod = method;
					break;
				}
			}

			if (paramMethod == null) {
//				log.warn("@Consume annotation has no paramMethod find!!");
				return null;
			}
			StringBuilder builder = new StringBuilder("def  createObj(){");

			Map<String, Object> tmpMap = new LinkedHashMap<>();


			String[] methodNames = parameterNameDiscoverer.getParameterNames(paramMethod);
			int len = methodNames.length;
			for (int i = 0; i < len; i++) {

				if (allFields.containsKey(methodNames[i])) {

					builder.append("if( ");
					builder.append(methodNames[i]);
					builder.append("!=null){ ");
					builder.append("__obj.");
					builder.append(methodNames[i]);
					builder.append("=");
					builder.append(methodNames[i]);
					builder.append("};");

				}

				tmpMap.put(methodNames[i], args[i]);

			}
			builder.append("return __obj  } val = createObj();");

			cacheMap.put(key, builder.toString());
			valExec = new HashMap<>(cacheMap.size() << 1);
			valExec.put("__obj", newInstanceClass.newInstance());
			valExec.putAll(tmpMap);
			cacheMap.put(key + "_map", tmpMap);
			expression = builder.toString();
		}

		ConsumeHelper.setObj(MVEL.eval(expression, valExec));

		return joinPoint.proceed();

	}

	@After("consumeEntity()")
	public void remote() {
		ConsumeHelper.delObj();
	}

	private Map<String, Boolean> getAllFields(Class<?> aClass) {

		String key = aClass.getCanonicalName();

		Map<String, Boolean> fieldMap = fieldsCache.getOrDefault(key, new HashMap<>());

		if (!fieldMap.isEmpty()) {
			return fieldMap;
		}

		Field[] fieldArr = aClass.getDeclaredFields();

		for (Field field : fieldArr) {

			if (Modifier.isFinal(field.getModifiers())) {
				continue;
			}

			fieldMap.put(field.getName(), Boolean.TRUE);


		}


		while (!aClass.getCanonicalName().equals(Object.class.getCanonicalName())) {
			aClass = aClass.getSuperclass();
			fieldArr = aClass.getDeclaredFields();
			for (Field field : fieldArr) {
				if (Modifier.isProtected(field.getModifiers()) || Modifier.isProtected(field.getModifiers())
					&& !Modifier.isFinal(field.getModifiers())) {
					fieldMap.put(field.getName(), Boolean.TRUE);
				}

			}


		}


		fieldsCache.put(key, fieldMap);

		return fieldMap;
	}

	private Class getConsume(ProceedingJoinPoint joinPoint) {
		MethodSignature joinPointObject = (MethodSignature) joinPoint.getSignature();
		Method method = joinPointObject.getMethod();

		if (method.isAnnotationPresent(Consume.class)) {
			Consume annotation = method.getAnnotation(Consume.class);
			return annotation.value();
		} else {
			Consume consume = AnnotationUtils.findAnnotation(joinPointObject.getMethod().getDeclaringClass(), Consume.class);
			if (consume != null) {
				return consume.value();
			} else {
				return null;
			}
		}
	}


}
